热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

感觉|简写_iOS开发断点调试高级技巧

篇首语:本文由编程笔记#小编为大家整理,主要介绍了iOS开发断点调试高级技巧相关的知识,希望对你有一定的参考价值。关于LLDB

篇首语:本文由编程笔记#小编为大家整理,主要介绍了iOS开发断点调试高级技巧相关的知识,希望对你有一定的参考价值。




关于LLDB调试,很多ios开发者可能就是停留在会下简单的断点,使用最多命令也就是po。无可厚非,这些简单的调试对于简单的问题来说应该是游刃有余。但是如果稍微复杂一些的问题,比如我之前遇到过友盟SDK里面的一个问题。我很想往里面下一个断点,可是对于.a的静态库来说,这根本不可能,最终还是我们组大牛使用命令的方式下了断点解决了这个问题。感觉这些知识很有必要,我于是把LLDB的基本调试命令都学习了一下,并在此与大家分享。


虽然博客很长,不过耐心看完,然后动手实践,一定会有很大帮助。


breakpoint

给某个文件的某一行下断点。可以使用如下两种方法,比如我想给Foo.m文件的26行下一个断点。可以使用如下的方法。

(lldb) breakpoint set --file Foo.m --line 26

如果出现如下提示则说明设置断点成功

Breakpoint 2: where = BreakPointDemo`-[Foo foo] + 23 at Foo.m:26, address = 0x000000010b22e687


也可以使用简写的形式如下。

(lldb) breakpoint set -f Foo.m -l 26

当然我们也可以直接给某个函数下断点,可以使用下面两种方法

(lldb) breakpoint set --name foo
(lldb) breakpoint set -n foo

当然我们也可以在一次命令中为下多个函数下断点

(lldb) breakpoint set --name foo --name bar

我们也可以更明确的指定是方法,如果是C的方法,可以使用如下两种的方法打断点,第二种方法M需要大写

(lldb) breakpoint set --method cplusFoo
(lldb) breakpoint set -M cplusFoo

如果是OC的方法,可以使用以下两种方式打断点,第二种S需要大写

(lldb) breakpoint set --selector foo
(lldb) breakpoint set -S foo

如果是C语言,还是只能使用上面介绍的–name的方式,不能直接指定对应的方法

当然,还有一个必杀器,就是使用正则,匹配你要打断点的函数。这个不限语言

(lldb) breakpoint set -r cFoo
(lldb) breakpoint set -r foo

也可以指定加载的动态库

(lldb) breakpoint set --shlib foo.dylib --name foo
(lldb) breakpoint set -s foo.dylib -n foo

我们同样可以对命令进行简写。下面两个命令的效果是一样的

(lldb) breakpoint set -n "-[Foo foo]"
(lldb) br s -n "-[Foo foo]"

想要查看有多少断点可以使用

(lldb) breakpoint list

打印的结果如下

Current breakpoints:
1: file = '/Users/jianquan/Xcode/BreakPointDemo/BreakPointDemo/ViewController.m', line = 20, exact_match = 0, locations = 0 (pending)
2: file = '/Users/jianquan/Xcode/BreakPointDemo/BreakPointDemo/ViewController.mm', line = 33, exact_match = 0, locations = 1, resolved = 1, hit count = 0
2.1: where = BreakPointDemo`::-[ViewController viewDidLoad]() + 186 at ViewController.mm:34, address = 0x0000000105f8362a, resolved, hit count = 0
......

我们可以对断点进行相关的操作,比如在执行到2.1断点的时候打印追踪轨迹。bt是

(lldb) breakpoint command add 2.1
Enter your debugger command(s). Type 'DONE' to end.
> bt
> DONE

除了add,还要delete等命令,这些命令不需要死记硬背,可以使用help命令。

(lldb) help break command
add -- Add LLDB commands to a breakpoint, to be executed whenever the
breakpoint is hit. If no breakpoint is specified, adds the
commands to the last created breakpoint.
delete -- Delete the set of commands from a breakpoint.
list -- List the script or set of commands to be executed when the
breakpoint is hit.

要查看更详细的命令用途,使用help .比如查看add命令用法

(lldb) help break command add
......
Enter your Python command(s). Type 'DONE' to end.
> def breakpoint_output (bp_no):
> out_string = "Hit breakpoint number " + repr (bp_no)
> print out_string
> return True
> breakpoint_output (1)
> DONE

可以看到其实这里面的命令大部分是Python脚本,不熟悉Python,暂时还没有仔细研究。


watchpoint

这个主要是用于观察变量值的具体变化

比如我需要观察某个变量a的值变化,我可以使用如下命令

(lldb) watchpoint set variable a

成功添加watchpoint后结果如下。

Watchpoint created: Watchpoint 1: addr = 0x7fff5913ca3c size = 4 state = enabled type = w
declare @ '/Users/jianquan/Xcode/BreakPointDemo/BreakPointDemo/ViewController.mm:25'
watchpoint spec = 'a'
new value: 10

也可以在这里添加.

然后我们可以设置在a的值变化为某个特定值之后触。

(lldb) watchpoint modify -c '(a=100)'

我们这个时候可以看一下具体断点的参数,使用watchpoint list命令

(lldb) watchpoint list
Number of supported hardware watchpoints: 4
Current watchpoints:
Watchpoint 1: addr = 0x7fff4fcb7a3c size = 4 state = enabled type = w
declare @ '/Users/jianquan/Xcode/BreakPointDemo/BreakPointDemo/ViewController.mm:25'
watchpoint spec = 'a'
new value: 10
condition = '(a=100)'

可以看到我们观察的变量的地址,声明变量的代码在第几行,已经具体的变量名是a,当前的值是10,触发的条件是'(a=100)'

然后我们执行如下命令,就可以看到断点到a的值变为100的地方

(lldb) c
Process 16596 resuming
2017-02-09 11:12:14.693 BreakPointDemo[16596:6050498] foo is foo
2017-02-09 11:12:14.693 BreakPointDemo[16596:6050498] bar is bar
Watchpoint 1 hit:
old value: 10
new value: 100

可以看到这个地方a的值已经发生改变。我们可以再使用watchpoint list命令看看具体值的变化

(lldb) watchpoint list
Number of supported hardware watchpoints: 4
Current watchpoints:
Watchpoint 1: addr = 0x7fff4fcb7a3c size = 4 state = enabled type = w
declare @ '/Users/jianquan/Xcode/BreakPointDemo/BreakPointDemo/ViewController.mm:25'
watchpoint spec = 'a'
old value: 10
new value: 100
condition = '(a=100)'

当然,还有一个特别好用的命令就是bt命令我们可以用它来追踪程序运行的过程。

(lldb) bt
* thread #1: tid = 0x5c52c2, 0x000000010ff465fe BreakPointDemo`::-[ViewController viewDidLoad](self=0x00007f932cc07c50, _cmd="viewDidLoad") + 158 at ViewController.mm:36, queue = 'com.apple.main-thread', stop reason = watchpoint 1
* frame #0: 0x000000010ff465fe BreakPointDemo`::-[ViewController viewDidLoad](self=0x00007f932cc07c50, _cmd="viewDidLoad") + 158 at ViewController.mm:36
frame #1: 0x000000011112ba3d UIKit`-[UIViewController loadViewIfRequired] + 1258
......

我们可以使用frame命令查看变量a的具体值。

(lldb) frame variable a
(int) a = 100

最后补充一点watchpoint list的东西。这个命令包括了三个可选参数,我们可以使用help命令查看具体的值

(lldb) help watchpoint list
-b ( --brief )
Give a brief description of the watchpoint (no location info).
-f ( --full )
Give a full description of the watchpoint and its locations.
-v ( --verbose )
Explain everything we know about the watchpoint (for debugging
debugger bugs).

-b是比较简略的信息,-f是比较全面的信息,-v是完整的信息。经过我的实验,如果使用watchpoint list,默认的是 watchpoint list -f


process

使用process命令也可以做很多有趣的操作。具体能做什么,我们也可使用help命令查看

(lldb) process help
attach -- Attach to a process.
connect -- Connect to a remote debug service.
continue -- Continue execution of all threads in the current process.
detach -- Detach from the current target process.
handle -- Manage LLDB handling of OS signals for the current target
......

查看更详细的命令使用help 。比如

(lldb) help process attach

这些命令在我目前日常开发中其实不怎么使用,可能我功力还不足吧。


thread

其实这个功能主要就是断点调试里面的如下这个功能。

我们可以使用thread命令来做一些断点的操作,具体有那些命令我们可以使用thread help进行查看。

(lldb) thread help
......
select -- Change the currently selected thread.
step-in -- Source level single step, stepping into calls.
Defaults to current thread unless specified.
step-inst -- Instruction level single step, stepping into calls.
Defaults to current thread unless specified.
step-inst-over -- Instruction level single step, stepping over calls.
Defaults to current thread unless specified.
step-out -- Finish executing the current stack frame and stop after
returning. Defaults to current thread unless
specified.
step-over -- Source level single step, stepping over calls.
Defaults to current thread unless specified.
step-scripted -- Step as instructed by the script class passed in the -C
option.
until -- Continue until a line number or address is reached by
the current or specified thread. Stops when returning
from the current function as a safety measure.

用得比较多的应该是 step-开头的这几个命令,使用起来很容易。我个人感觉比用鼠标点击断点好用多了~

EXAMINING THREAD STATE

这个使用的也主要还是thread命令,主要是使用以下几个命令。

检查当前进程的状态,可以使用如下命令。

lldb) thread list
Process 22323 stopped
* thread #1: tid = 0x62d0d7, 0x00000001082185fe BreakPointDemo`::-[ViewController viewDidLoad](self=0x00007ff81b60ab20, _cmd="viewDidLoad") + 158 at ViewController.mm:36, queue = 'com.apple.main-thread', stop reason = step until
......

*表明的就是当前的线程,可以使用如下的命令得到线程的回溯,这个词我也不确定怎么表达好,backtrace,也可以说是追踪。

lldb) thread backtrace
* thread #1: tid = 0x62d0d7, 0x00000001082185fe BreakPointDemo`::-[ViewController viewDidLoad](self=0x00007ff81b60ab20, _cmd="viewDidLoad") + 158 at ViewController.mm:36, queue = 'com.apple.main-thread', stop reason = step until
* frame #0: 0x00000001082185fe BreakPointDemo`::-[ViewController viewDidLoad](self=0x00007ff81b60ab20, _cmd="viewDidLoad") + 158 at ViewController.mm:36
frame #1: 0x00000001093fda3d UIKit`-[UIViewController loadViewIfRequired] + 1258
frame #2: 0x00000001093fde70 UIKit`-[UIViewController view] + 27
frame #3: 0x00000001092c74b5 UIKit`-[UIWindow addRootViewControllerViewIfPossible] + 71
frame #4: 0x00000001092c7c06 UIKit`-[UIWindow _setHidden:forced:] + 293
frame #5: 0x00000001092db519 UIKit`-[UIWindow makeKeyAndVisible] + 42
frame #6: 0x0000000109253f8d UIKit`-[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4818
frame #7: 0x000000010925a0ed UIKit`-[UIApplication _runWithMainScene:transitionContext:completion:] + 1731
frame #8: 0x000000010925726d UIKit`-[UIApplication workspaceDidEndTransaction:] + 188
frame #9: 0x000000010c3886cb FrontBoardServices`__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 24
frame #10: 0x000000010c388544 FrontBoardServices`-[FBSSerialQueue _performNext] + 189
frame #11: 0x000000010c3888cd FrontBoardServices`-[FBSSerialQueue _performNextFromRunLoopSource] + 45
frame #12: 0x0000000108ddc761 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #13: 0x0000000108dc198c CoreFoundation`__CFRunLoopDoSources0 + 556
frame #14: 0x0000000108dc0e76 CoreFoundation`__CFRunLoopRun + 918
frame #15: 0x0000000108dc0884 CoreFoundation`CFRunLoopRunSpecific + 420
frame #16: 0x0000000109255aea UIKit`-[UIApplication _run] + 434
frame #17: 0x000000010925bc68 UIKit`UIApplicationMain + 159
frame #18: 0x000000010821899f BreakPointDemo`main(argc=1, argv=0x00007fff579e7600) + 111 at main.m:14
frame #19: 0x000000010bbee68d libdyld.dylib`start + 1

当然我们如果想看所有线程的backtrace,可以使用thread backtrace all命令。内容太多,我这里就不演示log输出了。

如果我们想单独查看某个线程,我们可以先使用thread select 2跳到某个具体的线程,然后再进行其他操作,比如thread backtrace

EXAMINING STACK FRAME STATE

为了方便的观测架构参数和本地变量,我们可以使用 frame variable 命令

如果我什么参数也不加,将会把所有的参数和本地变量到打印出来。

(lldb) frame variable
(ViewController *) self = 0x00007ff81b60ab20
(SEL) _cmd = "viewDidLoad"
(int) a = 100
(Foo *) foo = 0x000061800000e820
(BreakPointDemoNameSpace::BreakPointClass *) cplusFoo = 0x3ff0000000000000

要打印某个变量需要在参数里面指定,这个命令我们在前面也使用过,比如要查看self

(lldb) frame variable self
(ViewController *) self = 0x00007ff81b60ab20

更进一步,我们可以查看一些子元素

(lldb) frame variable self->isa
(Class) self->isa = ViewController

命令虽然不是完整的表达式解释器,当时可以识别一些基本的操作 比如 &, *, ->, [],不是重载运算符,数组也可以使用,因为数组本身也是指针。

(lldb) frame variable *self
(ViewController) *self =
UIViewController =
UIResponder =
NSObject =
isa = ViewController

......

和之前thread命令很类似,我可以使用frame select去选择另外的一个frame

(lldb) frame select 9

如果想看更复杂的数据,我们可以使用expression命令

(lldb) expression self
(ViewController *) $0 = 0x00007fefa4705110

更复杂一些,我们可以用来输出一个表达式

(lldb) expr (int) printf ("I have a pointer 0x%llx.\\n", self)
I have a pointer 0x7fefa4705110.
(int) $1 = 33

我们可以继续以之前的命令来操作

(lldb) expr self = $0
(ViewController *) $2 = 0x00007fefa4705110

当然这个expr用途感觉不大。


call

其实这个命令完全可以使用po进行替代,call一般可以用来调用不需要返回值的调试命令,比如更改View的背景颜色,以下两个命令都可以达到相似的作用,更改当前View的背景颜色值。

(lldb) po [self.view setBackgroundColor:[UIColor redColor]]
(lldb) call [self.view setBackgroundColor:[UIColor redColor]]

image

虽然只是一个简单的命令,但是我还是感觉这是一个比较重要也比较实用的命令, 命令可用于寻址。比较实用的用法是用于寻找栈地址对应的代码位置。 下面我写了一段代码

//测试image命令使用
NSArray *arr=[[NSArray alloc] initWithObjects:@"1",@"2", nil];
NSLog(@"%@",arr[2]);

可以很明显的看到数组越界了,然后我们运行程序,可以看到程序报如下错误

this is cplusFoothis is cFoo2017-02-09 16:33:52.143 BreakPointDemo[26121:6901793] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 1]'
*** First throw call stack:
(
0 CoreFoundation 0x0000000104d67d4b __exceptionPreprocess + 171
1 libobjc.A.dylib 0x000000010471e21e objc_exception_throw + 48
2 CoreFoundation 0x0000000104ca22bb -[__NSArrayI objectAtIndex:] + 155
3 BreakPointDemo -[ViewController viewDidLoad] + 340
4 UIKit 0x000000010675ba3d -[UIViewController loadViewIfRequired] + 1258
5 UIKit 0x000000010675be70 -[UIViewController view] + 27
6 UIKit 0x00000001066254b5 -[UIWindow addRootViewControllerViewIfPossible] + 71
7 UIKit 0x0000000106625c06 -[UIWindow _setHidden:forced:] + 293
8 UIKit 0x0000000106639519 -[UIWindow makeKeyAndVisible] + 42
9 UIKit 0x00000001065b1f8d -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4818
10 UIKit 0x00000001065b80ed -[UIApplication _runWithMainScene:transitionContext:completion:] + 1731
11 UIKit 0x00000001065b526d -[UIApplication workspaceDidEndTransaction:] + 188
12 FrontBoardServices 0x00000001083456cb __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 24
13 FrontBoardServices 0x0000000108345544 -[FBSSerialQueue _performNext] + 189
14 FrontBoardServices 0x00000001083458cd -[FBSSerialQueue _performNextFromRunLoopSource] + 45
15 CoreFoundation 0x0000000104d0c761 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
16 CoreFoundation 0x0000000104cf198c __CFRunLoopDoSources0 + 556
17 CoreFoundation 0x0000000104cf0e76 __CFRunLoopRun + 918
18 CoreFoundation 0x0000000104cf0884 CFRunLoopRunSpecific + 420
19 UIKit 0x00000001065b3aea -[UIApplication _run] + 434
20 UIKit 0x00000001065b9c68 UIApplicationMain + 159
21 BreakPointDemo 0x000000010414794f main + 111
22 libdyld.dylib 0x00000001062b068d start + 1
23 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

我们大概可以猜测程序是崩溃在第三行log,也就是地址为0x0000000104147544的地方,怎么来呢,瞎猜的,哈哈。其实原理很简单,因为我的Demo名字叫BreakPointDemo。其他的名字很明显是系统的库。虽然log的21行也有BreakPointDemo,但是经过观察应该是main函数,不在考虑范围之内。

我们使用imagelookup命令,可以很快的定位到具体的代码行。

(lldb) image lookup --address 0x0000000104147544
Address: BreakPointDemo[0x0000000100001544] (BreakPointDemo.__TEXT.__text + 644)
Summary: BreakPointDemo`::-[ViewController viewDidLoad]() + 340 at ViewController.mm:46

看看我们的Xcode文件的代码。确实是46行

当然还有很多的命令我们可以探索,使用image help可以查看,这些命令我暂时没有接触过,后续工作或者学习中使用到了我会更新上来。


为命令设置别名

比如pframe variable的别名,p view实际上是frame variable view。除了系统自建的LLDB别名,你也可以自定义别名。比如下面这个命令。掌握了规律之后,任何的命令我们都可以自己设置别名。

(lldb) command alias bfl breakpoint set -f %1 -l %2
(lldb) bfl Foo.m 12

如果想要撤销别名使用

(lldb) command unalias bfl

当然还有一些LLDB的具体命令,我们可以在官网查看: The LLDB Debugger

Demo地址


总结

这么长的文章,看到这里真的不容易,不过我相信你应该有所收获了。另外我的博客长期欢迎评论留言,相互探讨,不足之处欢迎批准指正。


推荐阅读
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 本文介绍了响应式页面的概念和实现方式,包括针对不同终端制作特定页面和制作一个页面适应不同终端的显示。分析了两种实现方式的优缺点,提出了选择方案的建议。同时,对于响应式页面的需求和背景进行了讨论,解释了为什么需要响应式页面。 ... [详细]
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
  • 本文整理了315道Python基础题目及答案,帮助读者检验学习成果。文章介绍了学习Python的途径、Python与其他编程语言的对比、解释型和编译型编程语言的简述、Python解释器的种类和特点、位和字节的关系、以及至少5个PEP8规范。对于想要检验自己学习成果的读者,这些题目将是一个不错的选择。请注意,答案在视频中,本文不提供答案。 ... [详细]
  • 带添加按钮的GridView,item的删除事件
    先上图片效果;gridView无数据时显示添加按钮,有数据时,第一格显示添加按钮,后面显示数据:布局文件:addr_manage.xml<?xmlve ... [详细]
  • OpenMap教程4 – 图层概述
    本文介绍了OpenMap教程4中关于地图图层的内容,包括将ShapeLayer添加到MapBean中的方法,OpenMap支持的图层类型以及使用BufferedLayer创建图像的MapBean。此外,还介绍了Layer背景标志的作用和OMGraphicHandlerLayer的基础层类。 ... [详细]
  • 在加载一个第三方厂商的dll文件时,提示“找不到指定模块,加载失败”。由于缺乏必要的技术支持,百思不得期间。后来发现一个有用的工具 ... [详细]
  • 2021年最详细的Android屏幕适配方案汇总
    1Android屏幕适配的度量单位和相关概念建议在阅读本文章之前,可以先阅读快乐李同学写的文章《Android屏幕适配的度量单位和相关概念》,这篇文章 ... [详细]
  • 5G新空口关键技术之信道编码
    信道编码概念  信道编码过程包括添加循环冗余校验码(CRC,CyclicRedundancyCheck)、码块分割(Code ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文介绍了在Linux下安装Perl的步骤,并提供了一个简单的Perl程序示例。同时,还展示了运行该程序的结果。 ... [详细]
  • Ihaveaworkfolderdirectory.我有一个工作文件夹目录。holderDir.glob(*)>holder[ProjectOne, ... [详细]
author-avatar
zgshenxz_474
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有